From: Carl Lerche Date: Wed, 19 Mar 2014 01:10:48 +0000 (-0700) Subject: Initial stab at integration tests X-Git-Tag: archive/raspbian/0.35.0-2+rpi1~3^2^2^2^2^2^2^2~1130 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/success//%22http:/www.example.com/cgi/success/?a=commitdiff_plain;h=d1ec90b3d867e8115f81a1328532b5e7d5851ccc;p=cargo.git Initial stab at integration tests At the same time, we started adding a generic error handling concept to Cargo. The idea is that every Result gets converted to a CargoError, which includes all the information that Cargo needs to print out a friendly human error message and exit gracefully. --- diff --git a/Makefile b/Makefile index af30c5eb0..7a2100fd8 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,8 @@ BINS = cargo-compile \ cargo-rustc \ cargo-verify-project -SRC = $(wildcard src/*.rs) +SRC = $(shell find src -name '*.rs') + DEPS = -L libs/hammer.rs/target -L libs/rust-toml/lib TOML = libs/rust-toml/lib/$(shell rustc --crate-file-name libs/rust-toml/src/toml/lib.rs) HAMMER = libs/hammer.rs/target/$(shell rustc --crate-type=lib --crate-file-name libs/hammer.rs/src/hammer.rs) @@ -25,14 +26,14 @@ $(HAMMER): $(wildcard libs/hammer.rs/src/*.rs) $(TOML): $(wildcard libs/rust-toml/src/toml/*.rs) cd libs/rust-toml && make -$(HAMCREST): $(wildcard libs/hamcrest-rust/src/*.rs) +$(HAMCREST): $(wildcard libs/hamcrest-rust/src/hamcrest/*.rs) cd libs/hamcrest-rust && make # === Cargo $(LIBCARGO): $(SRC) mkdir -p target - $(RUSTC) $(RUSTC_FLAGS) --out-dir target src/cargo.rs + $(RUSTC) $(RUSTC_FLAGS) --out-dir target src/cargo/mod.rs touch $(LIBCARGO) libcargo: $(LIBCARGO) @@ -42,8 +43,18 @@ libcargo: $(LIBCARGO) $(BIN_TARGETS): target/%: src/bin/%.rs $(HAMMER) $(TOML) $(LIBCARGO) $(RUSTC) $(RUSTC_FLAGS) $(DEPS) -Ltarget --out-dir target $< -test: - echo "testing" +# === Tests + +TEST_SRC = $(wildcard tests/*.rs) +TEST_DEPS = $(DEPS) -L libs/hamcrest-rust/target + +target/tests: $(BIN_TARGETS) $(HAMCREST) $(TEST_SRC) + $(RUSTC) --test --crate-type=lib $(TEST_DEPS) -Ltarget --out-dir target tests/tests.rs + +test-integration: target/tests + CARGO_BIN_PATH=$(PWD)/target/ $< + +test: test-integration clean: rm -rf target @@ -54,7 +65,8 @@ distclean: clean cd libs/rust-toml && make clean # Setup phony tasks -.PHONY: all clean distclean test libcargo +.PHONY: all clean distclean test test-integration libcargo # Disable unnecessary built-in rules .SUFFIXES: + diff --git a/libs/hamcrest-rust b/libs/hamcrest-rust index 95f531ad8..39f006244 160000 --- a/libs/hamcrest-rust +++ b/libs/hamcrest-rust @@ -1 +1 @@ -Subproject commit 95f531ad8c726a832f28d171bde2860c77ec7619 +Subproject commit 39f00624492fd648631041eadf1301a08cd2a482 diff --git a/libs/rust-toml b/libs/rust-toml index 894fdd9db..1389ceb42 160000 --- a/libs/rust-toml +++ b/libs/rust-toml @@ -1 +1 @@ -Subproject commit 894fdd9db6c50b9a70d1fc7d4e49c76e86921016 +Subproject commit 1389ceb42b2ae04dac40c8b2d4af8fe21823ecbc diff --git a/src/bin/cargo-compile.rs b/src/bin/cargo-compile.rs index 39e885b23..34ca4ee19 100644 --- a/src/bin/cargo-compile.rs +++ b/src/bin/cargo-compile.rs @@ -1,4 +1,5 @@ #[crate_id="cargo-compile"]; +#[allow(deprecated_owned_vector)]; extern crate serialize; extern crate hammer; diff --git a/src/bin/cargo-read-manifest.rs b/src/bin/cargo-read-manifest.rs index e8e224e11..5e99cf56f 100644 --- a/src/bin/cargo-read-manifest.rs +++ b/src/bin/cargo-read-manifest.rs @@ -1,4 +1,5 @@ #[crate_id="cargo-read-manifest"]; +#[allow(deprecated_owned_vector)]; extern crate cargo; extern crate hammer; @@ -9,7 +10,7 @@ use hammer::{FlagDecoder,FlagConfig,FlagConfiguration}; use serialize::{Decoder,Decodable}; use serialize::json::Encoder; use toml::from_toml; -use cargo::{Manifest,LibTarget,ExecTarget,Project}; +use cargo::{Manifest,LibTarget,ExecTarget,Project,CargoResult,CargoError,ToCargoError}; use std::path::Path; #[deriving(Decodable,Encodable,Eq,Clone,Ord)] @@ -41,21 +42,32 @@ impl FlagConfig for ReadManifestFlags { } fn main() { + match execute() { + Err(e) => { + println!("{}", e.message); + // TODO: Exit with error code + }, + _ => return + } +} + +fn execute() -> CargoResult<()> { let mut decoder = FlagDecoder::new::(std::os::args().tail()); let flags: ReadManifestFlags = Decodable::decode(&mut decoder); if decoder.error.is_some() { - fail!("Error: {}", decoder.error.unwrap()); + return Err(CargoError::new(decoder.error.unwrap(), 1)); } - let root = toml::parse_from_file(flags.manifest_path).unwrap(); + let manifest_path = flags.manifest_path; + let root = try!(toml::parse_from_file(manifest_path.clone()).to_cargo_error(format!("Couldn't parse Toml file: {}", manifest_path), 1)); let toml_manifest = from_toml::(root.clone()); let (lib, bin) = normalize(&toml_manifest.lib, &toml_manifest.bin); let manifest = Manifest{ - root: Path::new(flags.manifest_path).dirname_str().unwrap().to_owned(), + root: try!(Path::new(manifest_path.clone()).dirname_str().to_cargo_error(format!("Could not get dirname from {}", manifest_path), 1)).to_owned(), project: toml_manifest.project, lib: lib, bin: bin @@ -64,6 +76,8 @@ fn main() { let encoded: ~str = Encoder::str_encode(&manifest); println!("{}", encoded); + + Ok(()) } fn normalize(lib: &Option<~[SerializedLibTarget]>, bin: &Option<~[SerializedExecTarget]>) -> (~[LibTarget], ~[ExecTarget]) { @@ -98,7 +112,7 @@ fn normalize(lib: &Option<~[SerializedLibTarget]>, bin: &Option<~[SerializedExec let b = b_ref.clone(); let mut path = b.path.clone(); if path.is_none() { - path = Some(format!("src/bin/{}.rs", b.name.clone())); + path = Some(format!("src/{}.rs", b.name.clone())); } ExecTarget{ path: path.unwrap(), name: b.name } }); diff --git a/src/bin/cargo-rustc.rs b/src/bin/cargo-rustc.rs index 70c62a86b..4e55d37e9 100644 --- a/src/bin/cargo-rustc.rs +++ b/src/bin/cargo-rustc.rs @@ -1,4 +1,5 @@ #[crate_id="cargo-rustc"]; +#[allow(deprecated_owned_vector)]; extern crate toml; extern crate serialize; @@ -10,7 +11,7 @@ use std::io::process::{Process,ProcessConfig,InheritFd}; use serialize::json; use serialize::Decodable; use std::path::Path; -use cargo::Manifest; +use cargo::{Manifest,CargoResult,CargoError,ToCargoError}; /** cargo-rustc -- ...args @@ -19,23 +20,40 @@ use cargo::Manifest; */ fn main() { + match execute() { + Err(e) => { + write!(&mut std::io::stderr(), "{}", e.message); + // TODO: Exit with error code + }, + _ => return + } +} + +fn execute() -> CargoResult<()> { let mut reader = io::stdin(); - let input = reader.read_to_str().unwrap(); + let input = try!(reader.read_to_str().to_cargo_error(~"Cannot read stdin to a string", 1)); - let json = json::from_str(input).unwrap(); + let json = try!(json::from_str(input).to_cargo_error(format!("Cannot parse json: {}", input), 1)); let mut decoder = json::Decoder::new(json); let manifest: Manifest = Decodable::decode(&mut decoder); - let Manifest{ root, lib, .. } = manifest; + let Manifest{ root, lib, bin, .. } = manifest; + + let (crate_type, out_dir) = if lib.len() > 0 { + ( ~"lib", lib[0].path ) + } else if bin.len() > 0 { + ( ~"bin", bin[0].path ) + } else { + return Err(CargoError::new(~"bad manifest, no lib or bin specified", 1)); + }; let root = Path::new(root); - let out_dir = lib[0].path; let target = join(&root, ~"target"); let args = [ join(&root, out_dir), ~"--out-dir", target, - ~"--crate-type", ~"lib" + ~"--crate-type", crate_type ]; match io::fs::mkdir_recursive(&root.join("target"), io::UserRWX) { @@ -51,15 +69,25 @@ fn main() { config.program = "rustc"; config.args = args.as_slice(); - let mut p = Process::configure(config).unwrap(); + let mut p = try!(Process::configure(config).to_cargo_error(format!("Could not start process: rustc {}", args.as_slice()), 1)); let status = p.wait(); if status != std::io::process::ExitStatus(0) { fail!("Failed to execute") } + + Ok(()) } fn join(path: &Path, part: ~str) -> ~str { - path.join(part).as_str().unwrap().to_owned() + format!("{}", path.join(part).display()) +} + +fn vec_idx(v: ~[T], idx: uint) -> Option { + if idx < v.len() { + Some(v[idx]) + } else { + None + } } diff --git a/src/bin/cargo-verify-project.rs b/src/bin/cargo-verify-project.rs index 7ee47ae88..255a585e0 100644 --- a/src/bin/cargo-verify-project.rs +++ b/src/bin/cargo-verify-project.rs @@ -1,4 +1,5 @@ #[crate_id="cargo-verify-project"]; +#[allow(deprecated_owned_vector)]; extern crate toml; extern crate getopts; diff --git a/src/cargo.rs b/src/cargo.rs deleted file mode 100644 index 684b8c920..000000000 --- a/src/cargo.rs +++ /dev/null @@ -1,34 +0,0 @@ -#[crate_type="rlib"]; - -extern crate serialize; -use serialize::{Decoder}; - -#[deriving(Decodable,Encodable,Eq,Clone,Ord)] -pub struct Manifest { - project: ~Project, - root: ~str, - lib: ~[LibTarget], - bin: ~[ExecTarget] -} - -#[deriving(Decodable,Encodable,Eq,Clone,Ord)] -pub struct ExecTarget { - name: ~str, - path: ~str -} - -#[deriving(Decodable,Encodable,Eq,Clone,Ord)] -pub struct LibTarget { - name: ~str, - path: ~str -} - -//pub type LibTarget = Target; -//pub type ExecTarget = Target; - -#[deriving(Decodable,Encodable,Eq,Clone,Ord)] -pub struct Project { - name: ~str, - version: ~str, - authors: ~[~str] -} diff --git a/src/cargo/mod.rs b/src/cargo/mod.rs new file mode 100644 index 000000000..8ea9d9507 --- /dev/null +++ b/src/cargo/mod.rs @@ -0,0 +1,82 @@ +#[crate_id="cargo"]; +#[crate_type="rlib"]; + +#[allow(deprecated_owned_vector)]; + +extern crate serialize; +use serialize::{Decoder}; +use std::fmt; +use std::fmt::{Show,Formatter}; + +pub mod util; + +#[deriving(Decodable,Encodable,Eq,Clone,Ord)] +pub struct Manifest { + project: ~Project, + root: ~str, + lib: ~[LibTarget], + bin: ~[ExecTarget] +} + +#[deriving(Decodable,Encodable,Eq,Clone,Ord)] +pub struct ExecTarget { + name: ~str, + path: ~str +} + +#[deriving(Decodable,Encodable,Eq,Clone,Ord)] +pub struct LibTarget { + name: ~str, + path: ~str +} + +//pub type LibTarget = Target; +//pub type ExecTarget = Target; + +#[deriving(Decodable,Encodable,Eq,Clone,Ord)] +pub struct Project { + name: ~str, + version: ~str, + authors: ~[~str] +} + +pub type CargoResult = Result; + +pub struct CargoError { + message: ~str, + exit_code: uint +} + +impl CargoError { + pub fn new(message: ~str, exit_code: uint) -> CargoError { + CargoError { message: message, exit_code: exit_code } + } +} + +impl Show for CargoError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f.buf, "{}", self.message) + } +} + +pub trait ToCargoError { + fn to_cargo_error(self, message: ~str, exit_code: uint) -> Result; +} + +impl ToCargoError for Result { + fn to_cargo_error(self, message: ~str, exit_code: uint) -> Result { + match self { + Err(_) => Err(CargoError{ message: message, exit_code: exit_code }), + Ok(val) => Ok(val) + } + } +} + +impl ToCargoError for Option { + fn to_cargo_error(self, message: ~str, exit_code: uint) -> CargoResult { + match self { + None => Err(CargoError{ message: message, exit_code: exit_code }), + Some(val) => Ok(val) + } + } +} diff --git a/src/cargo/util/mod.rs b/src/cargo/util/mod.rs new file mode 100644 index 000000000..27784a1ec --- /dev/null +++ b/src/cargo/util/mod.rs @@ -0,0 +1,2 @@ +pub use self::process_builder::process; +pub mod process_builder; diff --git a/src/cargo/util/process_builder.rs b/src/cargo/util/process_builder.rs new file mode 100644 index 000000000..821fa4789 --- /dev/null +++ b/src/cargo/util/process_builder.rs @@ -0,0 +1,63 @@ +use std; +use std::os; +use std::path::Path; +use std::io::IoResult; +use std::io::process::{Process,ProcessConfig,ProcessOutput}; +use ToCargoError; +use CargoResult; + +pub struct ProcessBuilder { + program: ~str, + args: ~[~str], + path: ~[~str], + cwd: Path +} + +// TODO: Upstream a Windows/Posix branch to Rust proper +static PATH_SEP : &'static str = ":"; + +impl ProcessBuilder { + pub fn args(mut self, arguments: &[~str]) -> ProcessBuilder { + self.args = arguments.to_owned(); + self + } + + pub fn extra_path(mut self, path: &str) -> ProcessBuilder { + self.path.push(path.to_owned()); + self + } + + pub fn cwd(mut self, path: Path) -> ProcessBuilder { + self.cwd = path; + self + } + + pub fn exec_with_output(self) -> CargoResult { + let mut config = ProcessConfig::new(); + + println!("cwd: {}", self.cwd.display()); + + config.program = self.program.as_slice(); + config.args = self.args.as_slice(); + config.cwd = Some(&self.cwd); + + let os_path = try!(os::getenv("PATH").to_cargo_error(~"Could not find the PATH environment variable", 1)); + let path = os_path + PATH_SEP + self.path.connect(PATH_SEP); + + let path = [(~"PATH", path)]; + config.env = Some(path.as_slice()); + + println!("{:?}", config); + + Process::configure(config).map(|mut ok| ok.wait_with_output()).to_cargo_error(~"Could not spawn process", 1) + } +} + +pub fn process(cmd: &str) -> ProcessBuilder { + ProcessBuilder { + program: cmd.to_owned(), + args: ~[], + path: ~[], + cwd: os::getcwd() + } +} diff --git a/tests/support.rs b/tests/support.rs new file mode 100644 index 000000000..0c5242dff --- /dev/null +++ b/tests/support.rs @@ -0,0 +1,123 @@ +// use std::io::fs::{mkdir_recursive,rmdir_recursive}; +use std::io::fs; +use std::os::tmpdir; +use std::path::{Path}; + +static CARGO_INTEGRATION_TEST_DIR : &'static str = "cargo-integration-tests"; +static MKDIR_PERM : u32 = 0o755; + +#[deriving(Eq,Clone)] +struct FileBuilder { + path: Path, + body: ~str +} + +impl FileBuilder { + pub fn new(path: Path, body: &str) -> FileBuilder { + FileBuilder { path: path, body: body.to_owned() } + } + + fn mk(&self) -> Result<(), ~str> { + try!(mkdir_recursive(&self.dirname())); + + let mut file = try!( + fs::File::create(&self.path) + .with_err_msg(format!("Could not create file; path={}", self.path.display()))); + + file.write_str(self.body.as_slice()) + .with_err_msg(format!("Could not write to file; path={}", self.path.display())) + } + + fn dirname(&self) -> Path { + Path::new(self.path.dirname()) + } +} + +#[deriving(Eq,Clone)] +struct ProjectBuilder { + name: ~str, + root: Path, + files: ~[FileBuilder] +} + +impl ProjectBuilder { + pub fn new(name: &str, root: Path) -> ProjectBuilder { + ProjectBuilder { + name: name.to_owned(), + root: root, + files: ~[] + } + } + + pub fn root(&self) -> Path { + self.root.clone() + } + + pub fn file(mut self, path: &str, body: &str) -> ProjectBuilder { + self.files.push(FileBuilder::new(self.root.join(path), body)); + self + } + + // TODO: return something different than a ProjectBuilder + pub fn build(self) -> ProjectBuilder { + match self.build_with_result() { + Err(e) => fail!(e), + _ => return self + } + } + + pub fn build_with_result(&self) -> Result<(), ~str> { + // First, clean the directory if it already exists + try!(self.rm_root()); + + // Create the empty directory + try!(mkdir_recursive(&self.root)); + + for file in self.files.iter() { + try!(file.mk()); + } + + println!("{}", self.root.display()); + println!("{:?}", self); + Ok(()) + } + + fn rm_root(&self) -> Result<(), ~str> { + if self.root.exists() { + rmdir_recursive(&self.root) + } + else { + Ok(()) + } + } +} + +// Generates a project layout +pub fn project(name: &str) -> ProjectBuilder { + ProjectBuilder::new(name, tmpdir().join(CARGO_INTEGRATION_TEST_DIR)) +} + +// === Helpers === + +pub fn mkdir_recursive(path: &Path) -> Result<(), ~str> { + fs::mkdir_recursive(path, MKDIR_PERM) + .with_err_msg(format!("could not create directory; path={}", path.display())) +} + +pub fn rmdir_recursive(path: &Path) -> Result<(), ~str> { + fs::rmdir_recursive(path) + .with_err_msg(format!("could not rm directory; path={}", path.display())) +} + +trait ErrMsg { + fn with_err_msg(self, val: ~str) -> Result; +} + +impl ErrMsg for Result { + fn with_err_msg(self, val: ~str) -> Result { + match self { + Ok(val) => Ok(val), + Err(_) => Err(val) + } + } +} diff --git a/tests/test_cargo_compile.rs b/tests/test_cargo_compile.rs new file mode 100644 index 000000000..3cdc5630d --- /dev/null +++ b/tests/test_cargo_compile.rs @@ -0,0 +1,83 @@ +use std; +use support::project; +use hamcrest::{SelfDescribing,Description,Matcher,assert_that}; +use cargo; + +#[deriving(Clone,Eq)] +pub struct ExistingFile; + +impl SelfDescribing for ExistingFile { + fn describe_to(&self, desc: &mut Description) { + desc.append_text("an existing file"); + } +} + +impl Matcher for ExistingFile { + fn matches(&self, actual: &Path) -> bool { + actual.exists() + } + + fn describe_mismatch(&self, actual: &Path, desc: &mut Description) { + desc.append_text(format!("`{}` was missing", actual.display())); + } +} + +pub fn existing_file() -> ExistingFile { + ExistingFile +} + +fn setup() { + +} + +test!(cargo_compile_with_explicit_manifest_path { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [[bin]] + + name = "foo" + "#) + .file("src/foo.rs", r#" + fn main() { + println!("i am foo"); + }"#) + .build(); + + let output = cargo::util::process("cargo-compile") + .args([~"--manifest-path", ~"Cargo.toml"]) + .extra_path(target_path()) + .cwd(p.root()) + .exec_with_output(); + + match output { + Ok(out) => { + println!("out:\n{}\n", std::str::from_utf8(out.output)); + println!("err:\n{}\n", std::str::from_utf8(out.error)); + }, + Err(e) => println!("err: {}", e) + } + + assert_that(p.root().join("target/foo/bar"), existing_file()); + assert!(p.root().join("target/foo").exists(), "the executable exists"); + + let o = cargo::util::process("foo") + .extra_path(format!("{}", p.root().join("target").display())) + .exec_with_output() + .unwrap(); + + assert_eq!(std::str::from_utf8(o.output).unwrap(), "i am foo\n"); +}) + +// test!(compiling_project_with_invalid_manifest) + +fn target_path() -> ~str { + std::os::getenv("CARGO_BIN_PATH").unwrap_or_else(|| { + fail!("CARGO_BIN_PATH wasn't set. Cannot continue running test") + }) +} diff --git a/tests/tests.rs b/tests/tests.rs new file mode 100644 index 000000000..3c74087d7 --- /dev/null +++ b/tests/tests.rs @@ -0,0 +1,18 @@ +#[feature(macro_rules)]; +#[allow(deprecated_owned_vector)]; + +extern crate cargo; +extern crate hamcrest; + +macro_rules! test( + ($name:ident $expr:expr) => ( + #[test] + fn $name() { + setup(); + $expr; + } + ) +) + +mod support; +mod test_cargo_compile;